package com.gitee.wsl.transform.encode.sample

import com.gitee.wsl.base.Objects.equals
import com.gitee.wsl.ext.base.Arrays
import com.gitee.wsl.ext.base.Arrays.arraycopy
import com.gitee.wsl.ext.base.Arrays.copyOf
import com.gitee.wsl.ext.base.Arrays.copyOfRange
import kotlin.jvm.JvmOverloads
import kotlin.math.ceil
import kotlin.math.min
import kotlin.math.pow


/**
 * [Hashids](http://hashids.org/) 协议实现，以实现：
 *
 *  * 生成简短、唯一、大小写敏感并无序的hash值
 *  * 自然数字的Hash值
 *  * 可以设置不同的盐，具有保密性
 *  * 可配置的hash长度
 *  * 递增的输入产生的输出无法预测
 *
 *
 *
 *
 * 来自：[https://github.com/davidafsilva/java-hashids](https://github.com/davidafsilva/java-hashids)
 *
 *
 *
 *
 * `Hashids`可以将数字或者16进制字符串转为短且唯一不连续的字符串，采用双向编码实现，比如，它可以将347之类的数字转换为yr8之类的字符串，也可以将yr8之类的字符串重新解码为347之类的数字。<br></br>
 * 此编码算法主要是解决爬虫类应用对连续ID爬取问题，将有序的ID转换为无序的Hashids，而且一一对应。
 *
 *
 * @author david
 */
//class Hashids(
//    salt: CharArray, alphabet: CharArray, // 编码后最小的字符长度
//    private val minLength: Int,
//) /*: Encoder<LongArray, String>, Decoder<String, LongArray> */{
//    // algorithm properties
//    private val alphabet: CharArray
//
//    // 多个数字编解码的分界符
//    private val separators: CharArray
//    private val separatorsSet: Set<Char?>
//    private val salt: CharArray = copyOf(salt, salt.size)
//
//    // 补齐至 minLength 长度添加的字符列表
//    private val guards: CharArray
//
//    // endregion
//    /**
//     * 构造
//     *
//     * @param salt      加盐值
//     * @param alphabet  hash字母表
//     * @param minLength 限制最小长度，-1表示不限制
//     */
//    init {
//
//        // filter and shuffle separators
//        var tmpSeparators = shuffle(filterSeparators(DEFAULT_SEPARATORS, alphabet), this.salt)
//
//        // validate and filter the alphabet
//        var tmpAlphabet = validateAndFilterAlphabet(alphabet, tmpSeparators)
//
//        // check separator threshold
//        if (tmpSeparators.isEmpty() || ((tmpAlphabet.size / tmpSeparators.size).toDouble()) > SEPARATOR_THRESHOLD) {
//            val minSeparatorsSize = ceil(tmpAlphabet.size / SEPARATOR_THRESHOLD).toInt()
//            // check minimum size of separators
//            if (minSeparatorsSize > tmpSeparators.size) {
//                // fill separators from alphabet
//                val missingSeparators = minSeparatorsSize - tmpSeparators.size
//                tmpSeparators = copyOf(tmpSeparators, tmpSeparators.size + missingSeparators)
//                arraycopy(
//                    tmpAlphabet, 0, tmpSeparators,
//                    tmpSeparators.size - missingSeparators, missingSeparators
//                )
//                arraycopy(
//                    tmpAlphabet, 0, tmpSeparators,
//                    tmpSeparators.size - missingSeparators, missingSeparators
//                )
//                tmpAlphabet = copyOfRange(tmpAlphabet, missingSeparators, tmpAlphabet.size)
//            }
//        }
//
//        // shuffle the current alphabet
//        shuffle(tmpAlphabet, this.salt)
//
//        // check guards
//        this.guards = CharArray(ceil(tmpAlphabet.size / GUARD_THRESHOLD).toInt())
//        if (alphabet.size < 3) {
//            arraycopy(tmpSeparators, 0, guards, 0, guards.size)
//            this.separators = copyOfRange(tmpSeparators, guards.size, tmpSeparators.size)
//            this.alphabet = tmpAlphabet
//        } else {
//            arraycopy(tmpAlphabet, 0, guards, 0, guards.size)
//            this.separators = tmpSeparators
//            this.alphabet = copyOfRange(tmpAlphabet, guards.size, tmpAlphabet.size)
//        }
//
//        // create the separators set
//        separatorsSet = IntStream.range(0, separators.size)
//            .mapToObj({ idx -> separators[idx] })
//            .collect(Collectors.toSet())
//    }
//
//    /**
//     * 编码给定的16进制数字
//     *
//     * @param hexNumbers 16进制数字
//     * @return 编码后的值, `null` if `numbers` 是 `null`.
//     * @throws IllegalArgumentException 数字不支持抛出此异常
//     */
//    fun encodeFromHex(hexNumbers: String?): String? {
//        if (hexNumbers == null) {
//            return null
//        }
//
//        // remove the prefix, if present
//        val hex =
//            if (hexNumbers.startsWith("0x") || hexNumbers.startsWith("0X")) hexNumbers.substring(2) else hexNumbers
//
//        // get the associated long value and encode it
//        var values: LongStream = LongStream.empty()
//        val matcher: Matcher = HEX_VALUES_PATTERN.matcher(hex)
//        while (matcher.find()) {
//            val value: Long = BigInteger("1" + matcher.group(), 16).longValue()
//            values = LongStream.concat(values, LongStream.of(value))
//        }
//
//        return encode(values.toArray())
//    }
//
//    /**
//     * 编码给定的数字数组
//     *
//     * @param numbers 数字数组
//     * @return 编码后的值, `null` if `numbers` 是 `null`.
//     * @throws IllegalArgumentException 数字不支持抛出此异常
//     */
//      fun encode(vararg numbers: Long): String? {
//        if (numbers == null) {
//            return null
//        }
//
//        // copy alphabet
//        val currentAlphabet = copyOf(alphabet, alphabet.size)
//
//        // determine the lottery number
//        val lotteryId: Long = LongStream.range(0, numbers.size)
//            .reduce(0, { state, i ->
//                val number = numbers[i as Int]
//                require(number >= 0) { "invalid number: " + number }
//                state + number % (i + LOTTERY_MOD)
//            })
//        val lottery = currentAlphabet[(lotteryId % currentAlphabet.size).toInt()]
//
//        // encode each number
//        val global = StringBuilder()
//        IntStream.range(0, numbers.size)
//            .forEach({ idx ->
//                // derive alphabet
//                deriveNewAlphabet(currentAlphabet, salt, lottery)
//
//                // encode
//                val initialLength: Int = global.length
//                translate(numbers[idx], currentAlphabet, global, initialLength)
//
//                // prepend the lottery
//                if (idx == 0) {
//                    global.insert(0, lottery)
//                }
//
//                // append the separator, if more numbers are pending encoding
//                if (idx + 1 < numbers.size) {
//                    val n = numbers[idx] % (global.get(initialLength).code + 1)
//                    global.append(separators[(n % separators.size).toInt()])
//                }
//            })
//
//        // add the guards, if there's any space left
//        if (minLength > global.length) {
//            var guardIdx = ((lotteryId + lottery.code.toLong()) % guards.size).toInt()
//            global.insert(0, guards[guardIdx])
//            if (minLength > global.length) {
//                guardIdx = ((lotteryId + global.get(2).code.toLong()) % guards.size).toInt()
//                global.append(guards[guardIdx])
//            }
//        }
//
//        // add the necessary padding
//        var paddingLeft: Int = minLength - global.length
//        while (paddingLeft > 0) {
//            shuffle(currentAlphabet, copyOf(currentAlphabet, currentAlphabet.size))
//
//            val alphabetHalfSize = currentAlphabet.size / 2
//            val initialSize: Int = global.length
//            if (paddingLeft > currentAlphabet.size) {
//                // entire alphabet with the current encoding in the middle of it
//                val offset = alphabetHalfSize + (if (currentAlphabet.size % 2 == 0) 0 else 1)
//
//                global.insert(0, currentAlphabet, alphabetHalfSize, offset)
//                global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize)
//                // decrease the padding left
//                paddingLeft -= currentAlphabet.size
//            } else {
//                // calculate the excess
//                val excess: Int = currentAlphabet.size + global.length - minLength
//                val secondHalfStartOffset = alphabetHalfSize + java.lang.Math.floorDiv(excess, 2)
//                val secondHalfLength = currentAlphabet.size - secondHalfStartOffset
//                val firstHalfLength = paddingLeft - secondHalfLength
//
//                global.insert(0, currentAlphabet, secondHalfStartOffset, secondHalfLength)
//                global.insert(secondHalfLength + initialSize, currentAlphabet, 0, firstHalfLength)
//
//                paddingLeft = 0
//            }
//        }
//
//        return global.toString()
//    }
//
//    //-------------------------
//    // Decode
//    //-------------------------
//    /**
//     * 解码Hash值为16进制数字
//     *
//     * @param hash hash值
//     * @return 解码后的16进制值, `null` if `numbers` 是 `null`.
//     * @throws IllegalArgumentException if the hash is invalid.
//     */
//    fun decodeToHex(hash: String?): String? {
//        if (hash == null) {
//            return null
//        }
//
//        val sb= StringBuilder()
//        Arrays.stream(decode(hash))
//            .mapToObj({ i: Long -> i.toHexString() })
//            .forEach({ hex -> sb.append(hex, 1, hex.length) })
//        return sb.toString()
//    }
//
//    /**
//     * 解码Hash值为数字数组
//     *
//     * @param hash hash值
//     * @return 解码后的16进制值, `null` if `numbers` 是 `null`.
//     * @throws IllegalArgumentException if the hash is invalid.
//     */
//    fun decode(hash: String): LongArray? {
//        if (hash == null) {
//            return null
//        }
//
//        // create a set of the guards
//        val guardsSet: Set<Char?> = IntStream.range(0, guards.size)
//            .mapToObj({ idx -> guards[idx] })
//            .collect(Collectors.toSet())
//        // count the total guards used
//        val guardsIdx: IntArray = IntStream.range(0, hash.length)
//            .filter({ idx -> guardsSet.contains(hash.get(idx)) })
//            .toArray()
//        // get the start/end index base on the guards count
//        val startIdx: Int
//        val endIdx: Int
//        if (guardsIdx.size > 0) {
//            startIdx = guardsIdx[0] + 1
//            endIdx = if (guardsIdx.size > 1) guardsIdx[1] else hash.length
//        } else {
//            startIdx = 0
//            endIdx = hash.length
//        }
//
//        var decoded: LongStream = LongStream.empty()
//        // parse the hash
//        if (hash.length > 0) {
//            val lottery = hash.get(startIdx)
//
//            // create the initial accumulation string
//            val length = hash.length - guardsIdx.size - 1
//            var block: java.lang.StringBuilder = java.lang.StringBuilder(length)
//
//            // create the base salt
//            val decodeSalt = CharArray(alphabet.size)
//            decodeSalt[0] = lottery
//            val saltLength = if (salt.size >= alphabet.size) alphabet.size - 1 else salt.size
//            arraycopy(salt, 0, decodeSalt, 1, saltLength)
//            val saltLeft = alphabet.size - saltLength - 1
//
//            // copy alphabet
//            val currentAlphabet = copyOf(alphabet, alphabet.size)
//
//            for (i in startIdx + 1..<endIdx) {
//                if (false == separatorsSet.contains(hash.get(i))) {
//                    block.append(hash.get(i))
//                    // continue if we have not reached the end, yet
//                    if (i < endIdx - 1) {
//                        continue
//                    }
//                }
//
//                if (block.length > 0) {
//                    // create the salt
//                    if (saltLeft > 0) {
//                        arraycopy(
//                            currentAlphabet, 0, decodeSalt,
//                            alphabet.size - saltLeft, saltLeft
//                        )
//                    }
//
//                    // shuffle the alphabet
//                    shuffle(currentAlphabet, decodeSalt)
//
//                    // prepend the decoded value
//                    val n = translate(block.toString().toCharArray(), currentAlphabet)
//                    decoded = LongStream.concat(decoded, LongStream.of(n))
//
//                    // create a new block
//                    block = java.lang.StringBuilder(length)
//                }
//            }
//        }
//
//        // validate the hash
//        val decodedValue: LongArray = decoded.toArray()
//        require(equals<String?>(hash, encode(*decodedValue))) { "invalid hash: " + hash }
//
//        return decodedValue
//    }
//
//    private fun translate(
//        n: Long, alphabet: CharArray,
//        sb: java.lang.StringBuilder, start: Int,
//    ): java.lang.StringBuilder {
//        var input = n
//        do {
//            // prepend the chosen char
//            sb.insert(start, alphabet[(input % alphabet.size).toInt()])
//
//            // trim the input
//            input = input / alphabet.size
//        } while (input > 0)
//
//        return sb
//    }
//
//    private fun translate(hash: CharArray, alphabet: CharArray): Long {
//        var number: Long = 0
//
//        val alphabetMapping: Map<Char?, Int?> = IntStream.range(0, alphabet.size)
//            .mapToObj({ idx -> arrayOf<Any?>(alphabet[idx], idx) })
//            .collect(
//                Collectors.groupingBy(
//                    { arr -> arr[0] as Char? },
//                    Collectors.mapping(
//                        { arr -> arr[1] as Int? },
//                        Collectors.reducing(null, { a, b -> if (a == null) b else a })
//                    )
//                )
//            )
//
//        for (i in hash.indices) {
//            number += alphabetMapping.computeIfAbsent(hash[i], { k ->
//                throw java.lang.IllegalArgumentException("Invalid alphabet for hash")
//            }) * alphabet.size.toDouble().pow((hash.size - i - 1).toDouble()).toLong()
//        }
//
//        return number
//    }
//
//    private fun deriveNewAlphabet(alphabet: CharArray, salt: CharArray, lottery: Char): CharArray {
//        // create the new salt
//        val newSalt = CharArray(alphabet.size)
//
//        // 1. lottery
//        newSalt[0] = lottery
//        var spaceLeft = newSalt.size - 1
//        var offset = 1
//        // 2. salt
//        if (salt.size > 0 && spaceLeft > 0) {
//            val length = min(salt.size, spaceLeft)
//            arraycopy(salt, 0, newSalt, offset, length)
//            spaceLeft -= length
//            offset += length
//        }
//        // 3. alphabet
//        if (spaceLeft > 0) {
//            arraycopy(alphabet, 0, newSalt, offset, spaceLeft)
//        }
//
//        // shuffle
//        return shuffle(alphabet, newSalt)
//    }
//
//    private fun validateAndFilterAlphabet(alphabet: CharArray, separators: CharArray): CharArray {
//        // validate size
//        require(alphabet.size >= MIN_ALPHABET_LENGTH) {
//            String.format(
//                "alphabet must contain at least %d unique " +
//                        "characters: %d", MIN_ALPHABET_LENGTH, alphabet.size
//            )
//        }
//
//        val seen: Set<Char?> = LinkedHashSet(alphabet.size)
//        val invalid: Set<Char?> = IntStream.range(0, separators.size)
//            .mapToObj({ idx -> separators[idx] })
//            .collect(Collectors.toSet())
//
//        // add to seen set (without duplicates)
//        IntStream.range(0, alphabet.size)
//            .forEach({ i ->
//                require(alphabet[i] != ' ') {
//                    java.lang.String.format(
//                        "alphabet must not contain spaces: " +
//                                "index %d", i
//                    )
//                }
//                val c = alphabet[i]
//                if (!invalid.contains(c)) {
//                    seen.add(c)
//                }
//            })
//
//        // create a new alphabet without the duplicates
//        val uniqueAlphabet = CharArray(seen.size())
//        var idx = 0
//        for (c in seen) {
//            uniqueAlphabet[idx++] = c!!
//        }
//        return uniqueAlphabet
//    }
//
//    private fun filterSeparators(separators: CharArray, alphabet: CharArray): CharArray {
//        val valid: Set<Char?> = IntStream.range(0, alphabet.size)
//            .mapToObj({ idx -> alphabet[idx] })
//            .collect(Collectors.toSet())
//
//        return IntStream.range(0, separators.size)
//            .mapToObj({ idx -> (separators[idx]) })
//            .filter(valid::contains) // ugly way to convert back to char[]
//            .map({ c -> java.lang.Character.toString(c) })
//            .collect(Collectors.joining())
//            .toCharArray()
//    }
//
//    private fun shuffle(alphabet: CharArray, salt: CharArray): CharArray {
//        var i = alphabet.size - 1
//        var v = 0
//        var p = 0
//        var j: Int
//        var z: Int
//        while (salt.isNotEmpty() && i > 0) {
//            v %= salt.size
//            z = salt[v].code
//            p += z
//            j = (z + v + p) % i
//            val tmp = alphabet[j]
//            alphabet[j] = alphabet[i]
//            alphabet[i] = tmp
//            i--
//            v++
//        }
//        return alphabet
//    }
//
//    companion object {
//        private const val LOTTERY_MOD = 100
//        private const val GUARD_THRESHOLD = 12.0
//        private const val SEPARATOR_THRESHOLD = 3.5
//
//        // 最小编解码字符串
//        private const val MIN_ALPHABET_LENGTH = 16
//        private val HEX_VALUES_PATTERN = "[\\w\\W]{1,12}".toRegex()
//
//        // 默认编解码字符串
//        val DEFAULT_ALPHABET: CharArray = charArrayOf(
//            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
//            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
//            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
//            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
//            '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
//        )
//
//        // 默认分隔符
//        private val DEFAULT_SEPARATORS = charArrayOf(
//            'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U'
//        )
//
//        /**
//         * 根据参数值，创建`Hashids`，使用默认[.DEFAULT_ALPHABET]作为字母表
//         *
//         * @param salt      加盐值
//         * @param minLength 限制最小长度，-1表示不限制
//         * @return `Hashids`
//         */
//        fun create(salt: CharArray, minLength: Int): Hashids {
//            return create(salt, DEFAULT_ALPHABET, minLength)
//        }
//
//        /**
//         * 根据参数值，创建`Hashids`
//         *
//         * @param salt      加盐值
//         * @param alphabet  hash字母表
//         * @param minLength 限制最小长度，-1表示不限制
//         * @return `Hashids`
//         */
//        // region create
//        /**
//         * 根据参数值，创建`Hashids`，使用默认[.DEFAULT_ALPHABET]作为字母表，不限制最小长度
//         *
//         * @param salt 加盐值
//         * @return `Hashids`
//         */
//        @JvmOverloads
//        fun create(salt: CharArray, alphabet: CharArray = DEFAULT_ALPHABET, minLength: Int = -1): Hashids {
//            return Hashids(salt, alphabet, minLength)
//        }
//    }
//}
